

#include "cybertron/core/PropertyVisitorXmlIO.hpp"
#include "cybertron/core/UtilString.hpp"
#include "pugixml.hpp"
#include <sstream>

CYBERTRON_BEGIN

class PropertyVisitorXmlIOImpl
{
public:
	std::shared_ptr<pugi::xml_document> doc;
	pugi::xml_node node;
	std::map<std::string, std::vector<std::shared_ptr<PropertyVisitorXmlIO>>> mCachedChildren;
};

CybertronCoreAPI PropertyVisitorXmlIO::PropertyVisitorXmlIO()
{
	mpImpl = new PropertyVisitorXmlIOImpl();
}

CybertronCoreAPI PropertyVisitorXmlIO::~PropertyVisitorXmlIO()
{
	delete mpImpl;
	mpImpl = nullptr;
}

CybertronCoreAPI void PropertyVisitorXmlIO::clear()
{
	mType = EPropertyVisitorType_Loader;
	mpImpl->doc = nullptr;
	mpImpl->node = pugi::xml_node();
	mpImpl->mCachedChildren.clear();
}

CybertronCoreAPI bool PropertyVisitorXmlIO::openStringWrite()
{
	clear();

	mType = EPropertyVisitorType_Saver;

	mpImpl->doc = std::make_shared<pugi::xml_document>();

	return true;
}

CybertronCoreAPI std::string PropertyVisitorXmlIO::getStringWrite() const
{
	if (mpImpl->doc != nullptr)
	{
		return std::string();
	}

	std::stringstream ss;
	mpImpl->doc->save(ss);
	return ss.str();
}

CybertronCoreAPI bool PropertyVisitorXmlIO::openStringRead(const std::string& xmlContent)
{
	mType = EPropertyVisitorType_Loader;

	mpImpl->doc = std::make_shared<pugi::xml_document>();

	pugi::xml_parse_result result = mpImpl->doc->load_buffer(xmlContent.c_str(), xmlContent.size());
	if (!result)
	{
		return false;
	}

	mpImpl->node = mpImpl->doc->root();

	return true;
}

CybertronCoreAPI std::string PropertyVisitorXmlIO::getData()
{
	return mpImpl->node.value();
}

CybertronCoreAPI void PropertyVisitorXmlIO::setData(const std::string& val)
{
	mpImpl->node.set_value(val.c_str());
}

CybertronCoreAPI bool PropertyVisitorXmlIO::visitProperty(const std::string& propertyName, bool& val)
{
	if (mType == EPropertyVisitorType_Loader)
	{
		val = mpImpl->node.attribute(propertyName.c_str()).as_bool();
	}
	else
	{
		mpImpl->node.append_attribute(propertyName.c_str()).set_value(val ? "true" : "false");
	}

	return true;
}

CybertronCoreAPI bool PropertyVisitorXmlIO::visitProperty(const std::string& propertyName, int& val)
{
	if (mType == EPropertyVisitorType_Loader)
	{
		val = mpImpl->node.attribute(propertyName.c_str()).as_int();
	}
	else
	{
		mpImpl->node.append_attribute(propertyName.c_str()).set_value(std::to_string(val).c_str());
	}

	return true;
}

CybertronCoreAPI bool PropertyVisitorXmlIO::visitProperty(const std::string& propertyName, unsigned int& val)
{
	if (mType == EPropertyVisitorType_Loader)
	{
		val = mpImpl->node.attribute(propertyName.c_str()).as_uint();
	}
	else
	{
		mpImpl->node.append_attribute(propertyName.c_str()).set_value(std::to_string(val).c_str());
	}

	return true;
}

CybertronCoreAPI bool PropertyVisitorXmlIO::visitProperty(const std::string& propertyName, double& val)
{
	if (mType == EPropertyVisitorType_Loader)
	{
		val = mpImpl->node.attribute(propertyName.c_str()).as_double();
	}
	else
	{
		mpImpl->node.append_attribute(propertyName.c_str()).set_value(std::to_string(val).c_str());
	}

	return true;
}

CybertronCoreAPI bool PropertyVisitorXmlIO::visitProperty(const std::string& propertyName, std::string& val)
{
	if (mType == EPropertyVisitorType_Loader)
	{
		val = mpImpl->node.attribute(propertyName.c_str()).as_string();
	}
	else
	{
		mpImpl->node.append_attribute(propertyName.c_str()).set_value(val.c_str());
	}

	return true;
}

CybertronCoreAPI size_t PropertyVisitorXmlIO::getChildSize(const std::string& propertyName)
{
	{
		auto it = mpImpl->mCachedChildren.find(propertyName);
		if (it != mpImpl->mCachedChildren.end())
		{
			return (*it).second.size();
		}
	}

	auto& cachedChildren = buildChildCacheOnLoad(propertyName);

	return cachedChildren.size();
}

CybertronCoreAPI PropertyVisitor* PropertyVisitorXmlIO::getChild(const std::string& propertyName, size_t i)
{
	auto it = mpImpl->mCachedChildren.find(propertyName);
	if (it != mpImpl->mCachedChildren.end())
	{
		if (i >= (*it).second.size())
		{
			return nullptr;
		}

		return ((*it).second[i]).get();
	}

	auto& cachedChildren = buildChildCacheOnLoad(propertyName);

	if (i >= cachedChildren.size())
	{
		return nullptr;
	}

	return cachedChildren[i].get();
}

std::vector<std::shared_ptr<PropertyVisitorXmlIO>>& 
PropertyVisitorXmlIO::buildChildCacheOnLoad(const std::string& propertyName)
{
	std::vector<std::shared_ptr<PropertyVisitorXmlIO>>& cachedChildren = mpImpl->mCachedChildren[propertyName];

	std::vector<std::string> path;
	UtilString::split(path, propertyName, '/');
	pugi::xml_node n = mpImpl->node;
	for (size_t i = 0; i < path.size(); ++i)
	{
		auto children = n.children(path[i].c_str());

		size_t numChildren = 0;
		for (auto it = children.begin(); it != children.end(); ++it)
		{
			numChildren++;
		}

		if (numChildren == 0)
		{
			return cachedChildren;
		}

		if (i + 1 < path.size())
		{
			n = *(children.begin());
		}
		else
		{
			cachedChildren.resize(numChildren);
			size_t index = 0;
			for (auto it = children.begin(); it != children.end(); ++it)
			{
				cachedChildren[index].reset(new PropertyVisitorXmlIO());
				cachedChildren[index]->mpImpl->doc = mpImpl->doc;
				cachedChildren[index]->mpImpl->node = (*it);
				index++;
			}
		}
	}

	return cachedChildren;
}

CybertronCoreAPI PropertyVisitor* PropertyVisitorXmlIO::addChild(const std::string& propertyName)
{
	std::shared_ptr<PropertyVisitorXmlIO> pProperty(new PropertyVisitorXmlIO());

	std::vector<std::string> path;
	UtilString::split(path, propertyName, '/');
	pugi::xml_node n = mpImpl->node;

	for (size_t i = 0; i < path.size(); ++i)
	{
		pugi::xml_node findN = n.find_child([=](const pugi::xml_node& n) {
			return n.name() == path[i];
		});
		if (findN.empty())
		{
			n = n.append_child(path[i].c_str());
		}
		else
		{
			n = findN;
		}
	}

	pProperty->mpImpl->doc = mpImpl->doc;
	pProperty->mpImpl->node = n;
	mpImpl->mCachedChildren[propertyName].push_back(pProperty);

	return pProperty.get();
}

CYBERTRON_END
